package org.tokaf.algorithm;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;

import org.tokaf.MyTopKCompareValues;
import org.tokaf.TopKElement;
import org.tokaf.bestcolumnfinder.BestColumnFinder;
import org.tokaf.bestcolumnfinder.MissingValuesFinder;
import org.tokaf.datasearcher.DataSearcher;
import org.tokaf.rater.Rater;

/**
 * <p> CombinedAlgorithm performs the same way as NRAAlgorithm, except every h
 * steps, it performs direct access for all atributes of element, which has
 * highest B rating and has some missing atributes.</p> <p>Copyright (c) 2006</p>
 * @author Alan Eckhardt
 * @version 1.0
 */
public class CombinedAlgorithm extends Algorithm {

	ArrayList<TopKElement> sortedK;

	ArrayList<TopKElement> candidatesNulls;

	TopKElement threshold = null;

	// Number of step to perform direct acces
	int h = 100;

	// Number of step to check algorithms end.
	int hEnd = 1;

	boolean add = true;

	// Constant of toleration
	double multiplicative = 1.0;

	// Constant of toleration
	double additive = 0;

	// Position in each list, when the threshold value was less than k-th value
	int exceededThreshold[] = null;

	MyTopKCompareValues comp = new MyTopKCompareValues();

	public CombinedAlgorithm(DataSearcher[] data, Rater rater,
			BestColumnFinder finder, String prefix, int K) {
		super(data, rater, finder, prefix, K);
		sortedK = new ArrayList<TopKElement>();
		candidatesNulls = new ArrayList<TopKElement>();
	}

	public void writeStats(String fileName, String[] additionalInfo) {
		try {
			BufferedWriter out = new BufferedWriter(new FileWriter(fileName,
					true));

			Calendar cas = Calendar.getInstance();

			out.write(cas.get(Calendar.YEAR) + "."
					+ (cas.get(Calendar.MONTH) + 1) + "."
					+ cas.get(Calendar.DAY_OF_MONTH) + " "
					+ cas.get(Calendar.HOUR_OF_DAY) + ":"
					+ cas.get(Calendar.MINUTE) + ";");

			out.write(this.getClass().getSimpleName() + ";");
			out.write(finder.getClass().getSimpleName() + ";");
			out.write(rater.getClass().getSimpleName() + ";");
			out.write(data[0].getClass().getSimpleName() + ";");
			out.write(data[0].getNormalizer().getClass().getSimpleName() + ";");

			out.write("" + K + ";");

			out.write("" + data.length + ";");

			out.write(bcfTime + ";");
			out.write(rateTime + ";");
			out.write(dataTime + ";");
			out.write(ratingTime + ";");
			out.write(checkEndTime + ";");
			out.write(sortTime + ";");
			out.write(searchTime + ";");
			out.write(step + ";");
			if (finder instanceof MissingValuesFinder) {
				out.write("" + ((MissingValuesFinder) finder).getDepth() + ";");
			} else
				out.write("" + 0 + ";");
			out.write(hEnd + ";");
			out.write(h + ";");
			for (int i = 0; i < additionalInfo.length; i++)
				out.write(additionalInfo[i] + ";");
			out.write("\n");

			out.close();
		} catch (IOException e) {
		}
	}

	/**
	 * @return Threshold value.
	 */
	public TopKElement getThreshold() {
		return threshold;
	}

	boolean testTopK(TopKElement threshold) {

		if (sortedK.size() < K)
			return false;
		double kthRating = sortedK.get(K - 1).rating;
		if (threshold.rating >= kthRating)
			return false;

		if (exceededThreshold == null) {
			exceededThreshold = new int[data.length];
			for (int i = 0; i < data.length; i++)
				exceededThreshold[i] = data[i].getPosistion();
		}
		add = false;

		for (int i = K; i < sortedK.size(); i++) {
			double actRating = rater.getRating(sortedK.get(i),
					data, threshold);
			// if ((actRating ) > (kthRating+ additive)* multiplicative) {
			if ((actRating) > (kthRating)) {
				// Removes all object that didn't pass the test before
				for (int j = K; j < i; j++) {
					candidatesNulls.remove(sortedK.get(K));
					sortedK.remove(K);
				}
				return false;
			}
		}
		return true;
	}

	private boolean hasNulls(TopKElement newEl) {
		for (int i = 0; i < newEl.getLength(); i++)
			if (newEl.isNull(i))
				return true;
		return false;
	}

	private void sortTopK(TopKElement newEl) {
		/*
		 * if(sortedK.size()<K){
		 * Collections.sort(sortedK.subList(0,sortedK.size()), new
		 * MyTopKCompareValues()); return; }
		 */

		if (!hasNulls(newEl))
			candidatesNulls.remove(newEl);
		int length = Math.min(K, sortedK.size());

		int pos = Collections.binarySearch(sortedK.subList(0, length), newEl,
				comp);

		if (pos < 0 && pos > -(length + 1)) {
			sortedK.remove(newEl);
			sortedK.add(-pos - 1, newEl);
		} else if (pos >= 0 && pos < (length)) {
			if (pos == sortedK.indexOf(newEl)) {
				sortedK.remove(newEl);
				if (length == 1) {
					sortedK.add(0, newEl);
					return;
				}
				pos = Collections.binarySearch(sortedK.subList(0, length - 1),
						newEl, comp);
				if (pos < 0 && pos > -(length + 1)) {
					sortedK.add(-pos - 1, newEl);
					return;
				}
				sortedK.add(newEl);
			}

			int i = pos;
			for (; (i < sortedK.size() && newEl.rating == sortedK
					.get(i).rating); i++) {
				if (
				// Ties are broken with maximal possible achievable rating
				// Instead of null values are used values of threshold
				(newEl.rating == sortedK.get(i).rating && rater
						.getRating(newEl, data, threshold) > rater.getRating(
						sortedK.get(i), data, threshold))) {
					sortedK.remove(newEl);
					sortedK.add(i, newEl);
					break;
				}
			}
			if (i < sortedK.size()
					&& newEl.rating > sortedK.get(i).rating) {
				sortedK.remove(newEl);
				sortedK.add(i, newEl);
			}
		} else {
		}
	}

	private void rate(TopKElement threshold, int i) {

		long startTime = System.nanoTime();
		Object subject = data[i].getField(0);
		Object object = data[i].getField(2);
		dataTime = dataTime + System.nanoTime() - startTime;

		/*
		 * boolean add = true; // If the threshold value is less than rating of
		 * k-th object, // it is futile to add new object to the list, it cannot
		 * be among top k objects. if (sortedK.size() >= K) { double kthRating =
		 * ((TopKElement) sortedK.get(K - 1)).rating; // Threshold isn't low
		 * enough, we add the new object if (threshold.rating < kthRating) { add =
		 * false; } }
		 */

		if (subject == null) {
			end = testTopK(threshold);
			return;
		}
		startTime = System.nanoTime();
		TopKElement newSubject = new TopKElement(subject.toString(), -1,
				data.length);
		TopKElement temp = findEntity(newSubject);
		searchTime = searchTime + System.nanoTime() - startTime;

		startTime = System.nanoTime();
		threshold.setRating(i, object, data[i]);
		// rater.AddRating(threshold, predicate.toString(), object);
		rater.rate(threshold, data);
		ratingTime = ratingTime + System.nanoTime() - startTime;

		if (temp == null && add) {
			topKlist.put(newSubject.name, newSubject);
			// candidates.add(newSubject);
			sortedK.add(newSubject);
			candidatesNulls.add(newSubject);
		} else
			newSubject = temp;

		if (newSubject != null) {
			startTime = System.nanoTime();
			newSubject.setFound(i, step);
			newSubject.setPosition(i, data[i].getPosistion());

			newSubject.setRating(i, object, data[i]);
			// rater.AddRating(newSubject, predicate.toString(), object);
			rater.rate(newSubject, data);
			ratingTime = ratingTime + System.nanoTime() - startTime;
			// Collections.sort(sortedK, new MyTopKCompareValues());
			startTime = System.nanoTime();
			sortTopK(newSubject);
			sortTime = sortTime + System.nanoTime() - startTime;
		}

		startTime = System.nanoTime();
		if (step % hEnd == 0)
			end = testTopK(threshold);
		checkEndTime = checkEndTime + System.nanoTime() - startTime;

		startTime = System.nanoTime();
		if (step % h == 0)
			seekDirect();
		dataTime = dataTime + System.nanoTime() - startTime;

		startTime = System.nanoTime();
		if (data[i].advance() == -1) {
			finished[i] = true;
		}
		dataTime = dataTime + System.nanoTime() - startTime;
	}

	private void seekDirect() {
		// Collections.sort(candidates, new MyTopKCompareValuesOptional(rater,
		// data, threshold));

		if (candidatesNulls.size() == 0)
			return;
		int max = 0;
		// We search for highest maximal rating B(el)
		for (int j = 0; j < candidatesNulls.size(); j++) {
			if (rater.getRating(candidatesNulls.get(j), data,
					threshold) > rater.getRating(candidatesNulls
					.get(max), data, threshold)) {
				max = j;
			}
		}

		TopKElement el = candidatesNulls.get(max);
		// We search directly for values of element with highesr maximal rating
		// B(el)
		for (int i = 0; i < el.getLength(); i++)
			if (el.isNull(i)) {
				el.setRating(i, data[i].findObject(el.name, 0, 2), data[i]);
			}
		rater.rate(el, data);
		this.sortTopK(el);
	}

	private void initThreshold(TopKElement threshold) {
		// Iniciuji prah na zacatek vsech seznamu
		for (int j = 0; j < data.length; j++) {
			if (!data[j].hasNext()) {
				finished[j] = true;
				continue;
			}
			Object object = data[j].getField(2);
			// rater.AddRating(threshold, predicate.toString(), object);
			threshold.setRating(j, object, data[j]);
		}
	}

	protected ArrayList<TopKElement> createArrayList() {
		return sortedK;
	}

	private ArrayList<TopKElement> runNRAAlgorithm() {
		// We are starting, so we initialize threshold
		if (threshold == null) {
			threshold = new TopKElement("threshold", -1, data.length);
			initThreshold(threshold);
		}
		while (end != true) {
			long startTime = System.nanoTime();

			int i = finder.findColumn(this);
			bcfTime += System.nanoTime() - startTime;
			// System.out.print(i+", ");
			if (i == -1) {
				end = true;
				// System.out.println();

				Collections.sort(sortedK, new MyTopKCompareValues());
				return createArrayList();
			}

			startTime = System.nanoTime();
			rate(threshold, i);
			rateTime += System.nanoTime() - startTime;

			/*
			 * if (sortedK.size() > K - 1 ) { kthRating = ((TopKElement)
			 * sortedK.get(K - 1)).rating; if(kthRating>threshold.rating){ for
			 * (int j = 0; j < candidates.size(); j++) { double actRating =
			 * rater.getRating((TopKElement) sortedK.get(j), data, threshold);
			 * if (actRating < kthRating) { candidates.remove(j); j--; } } } }
			 */

			step++;
			if (maxStep != -1 && maxStep >= step)
				break;
		}

		// System.out.println();
		Collections.sort(sortedK, new MyTopKCompareValues());
		return createArrayList();

	}

	public ArrayList<TopKElement> run() {
		long startTime = System.nanoTime();
		ArrayList<TopKElement> a;
		if (data.length > 0) {
			a = runNRAAlgorithm();
		} else
			a = new ArrayList<TopKElement>();
		runTime = System.nanoTime() - startTime;
		return a;
	}

	public int step() {
		if (threshold == null) {
			threshold = new TopKElement("threshold", -1, data.length);
			initThreshold(threshold);
		}
		if (end)
			return -1;
		actColumn = finder.findColumn(this);
		if (actColumn == -1) {
			end = true;
			return -1;
		}
		rate(threshold, actColumn);
		actColumn++;
		return actColumn - 1;
	}

	public int[] getExceededThreshold() {
		return exceededThreshold;
	}

	/**
	 * Gets the multiplicative coefficient
	 * @return the vaule of multiplicative coefficient
	 */
	double getMultiplicative() {
		return multiplicative;
	}

	/**
	 * Sets the multiplicative coefficient
	 * @param multiplicative double
	 * @return the new vaule of multiplicative coefficient
	 */
	double setMultiplicative(double multiplicative) {
		this.multiplicative = multiplicative;
		return this.multiplicative;
	}

	/**
	 * Gets the additive coefficient
	 * @return the vaule of additive coefficient
	 */
	double getAdditive() {
		return additive;
	}

	/**
	 * Sets the additive coefficient
	 * @param additive double
	 * @return the new vaule of additive coefficient
	 */
	double setAdditive(double additive) {
		this.additive = additive;
		return this.additive;
	}

	public int getHEnd() {
		return hEnd;
	}

	public void setHEnd(int hEnd) {
		this.hEnd = hEnd;
	}

	public int getH() {
		return h;
	}

	public void setH(int h) {
		this.h = h;
	}

}
